#include "keyboard.h"
#include "hal.h"
#include "ctype.h"

#include "vga_text.h"

#define KYBRD_ENC_CMD_PORT 0x60
#define KYBRD_ENC_INPUT_PORT 0x60

#define KYBRD_CTL_STATUS_PORT 0x64
#define KYBRD_CTL_CMD_PORT 0x64

enum KYBRD_ENC_CMDS {

	KYBRD_ENC_CMD_SET_LED				=	0xED,
	KYBRD_ENC_CMD_ECHO					=	0xEE,
	KYBRD_ENC_CMD_SCAN_CODE_SET			=	0xF0,
	KYBRD_ENC_CMD_ID					=	0xF2,
	KYBRD_ENC_CMD_AUTODELAY				=	0xF3,
	KYBRD_ENC_CMD_ENABLE				=	0xF4,
	KYBRD_ENC_CMD_RESETWAIT				=	0xF5,
	KYBRD_ENC_CMD_RESETSCAN				=	0xF6,
	KYBRD_ENC_CMD_ALL_AUTO				=	0xF7,
	KYBRD_ENC_CMD_ALL_MAKEBREAK			=	0xF8,
	KYBRD_ENC_CMD_ALL_MAKEONLY			=	0xF9,
	KYBRD_ENC_CMD_ALL_MAKEBREAK_AUTO	=	0xFA,
	KYBRD_ENC_CMD_SINGLE_AUTOREPEAT		=	0xFB,
	KYBRD_ENC_CMD_SINGLE_MAKEBREAK		=	0xFC,
	KYBRD_ENC_CMD_SINGLE_BREAKONLY		=	0xFD,
	KYBRD_ENC_CMD_RESEND				=	0xFE,
	KYBRD_ENC_CMD_RESET					=	0xFF
};

enum KYBRD_CTRL_STATS_MASK {

	KYBRD_CTRL_STATS_MASK_OUT_BUF	=	1,		//00000001
	KYBRD_CTRL_STATS_MASK_IN_BUF	=	2,		//00000010
	KYBRD_CTRL_STATS_MASK_SYSTEM	=	4,		//00000100
	KYBRD_CTRL_STATS_MASK_CMD_DATA	=	8,		//00001000
	KYBRD_CTRL_STATS_MASK_LOCKED	=	0x10,	//00010000
	KYBRD_CTRL_STATS_MASK_AUX_BUF	=	0x20,	//00100000
	KYBRD_CTRL_STATS_MASK_TIMEOUT	=	0x40,	//01000000
	KYBRD_CTRL_STATS_MASK_PARITY	=	0x80	//10000000
};

enum KYBRD_CTRL_CMDS {

	KYBRD_CTRL_CMD_READ				=	0x20,
	KYBRD_CTRL_CMD_WRITE			=	0x60,
	KYBRD_CTRL_CMD_SELF_TEST		=	0xAA,
	KYBRD_CTRL_CMD_INTERFACE_TEST	=	0xAB,
	KYBRD_CTRL_CMD_DISABLE			=	0xAD,
	KYBRD_CTRL_CMD_ENABLE			=	0xAE,
	KYBRD_CTRL_CMD_READ_IN_PORT		=	0xC0,
	KYBRD_CTRL_CMD_READ_OUT_PORT	=	0xD0,
	KYBRD_CTRL_CMD_WRITE_OUT_PORT	=	0xD1,
	KYBRD_CTRL_CMD_READ_TEST_INPUTS	=	0xE0,
	KYBRD_CTRL_CMD_SYSTEM_RESET		=	0xFE,
	KYBRD_CTRL_CMD_MOUSE_DISABLE	=	0xA7,
	KYBRD_CTRL_CMD_MOUSE_ENABLE		=	0xA8,
	KYBRD_CTRL_CMD_MOUSE_PORT_TEST	=	0xA9,
	KYBRD_CTRL_CMD_MOUSE_WRITE		=	0xD4
};

// scan error codes ------------------------------------------

enum KYBRD_ERROR {

	KYBRD_ERR_BUF_OVERRUN			=	0,
	KYBRD_ERR_ID_RET				=	0x83AB,
	KYBRD_ERR_BAT					=	0xAA,	//note: can also be L. shift key make code
	KYBRD_ERR_ECHO_RET				=	0xEE,
	KYBRD_ERR_ACK					=	0xFA,
	KYBRD_ERR_BAT_FAILED			=	0xFC,
	KYBRD_ERR_DIAG_FAILED			=	0xFD,
	KYBRD_ERR_RESEND_CMD			=	0xFE,
	KYBRD_ERR_KEY					=	0xFF
};

static int _kkybrd_scancode_std [] = {

	//! key			scancode
	KEY_UNKNOWN,	//0
	KEY_ESCAPE,		//1
	KEY_1,			//2
	KEY_2,			//3
	KEY_3,			//4
	KEY_4,			//5
	KEY_5,			//6
	KEY_6,			//7
	KEY_7,			//8
	KEY_8,			//9
	KEY_9,			//0xa
	KEY_0,			//0xb
	KEY_MINUS,		//0xc
	KEY_EQUAL,		//0xd
	KEY_BACKSPACE,	//0xe
	KEY_TAB,		//0xf
	KEY_Q,			//0x10
	KEY_W,			//0x11
	KEY_E,			//0x12
	KEY_R,			//0x13
	KEY_T,			//0x14
	KEY_Y,			//0x15
	KEY_U,			//0x16
	KEY_I,			//0x17
	KEY_O,			//0x18
	KEY_P,			//0x19
	KEY_LEFTBRACKET,//0x1a
	KEY_RIGHTBRACKET,//0x1b
	KEY_RETURN,		//0x1c
	KEY_LCTRL,		//0x1d
	KEY_A,			//0x1e
	KEY_S,			//0x1f
	KEY_D,			//0x20
	KEY_F,			//0x21
	KEY_G,			//0x22
	KEY_H,			//0x23
	KEY_J,			//0x24
	KEY_K,			//0x25
	KEY_L,			//0x26
	KEY_SEMICOLON,	//0x27
	KEY_QUOTE,		//0x28
	KEY_GRAVE,		//0x29
	KEY_LSHIFT,		//0x2a
	KEY_BACKSLASH,	//0x2b
	KEY_Z,			//0x2c
	KEY_X,			//0x2d
	KEY_C,			//0x2e
	KEY_V,			//0x2f
	KEY_B,			//0x30
	KEY_N,			//0x31
	KEY_M,			//0x32
	KEY_COMMA,		//0x33
	KEY_DOT,		//0x34
	KEY_SLASH,		//0x35
	KEY_RSHIFT,		//0x36
	KEY_KP_ASTERISK,//0x37
	KEY_RALT,		//0x38
	KEY_SPACE,		//0x39
	KEY_CAPSLOCK,	//0x3a
	KEY_F1,			//0x3b
	KEY_F2,			//0x3c
	KEY_F3,			//0x3d
	KEY_F4,			//0x3e
	KEY_F5,			//0x3f
	KEY_F6,			//0x40
	KEY_F7,			//0x41
	KEY_F8,			//0x42
	KEY_F9,			//0x43
	KEY_F10,		//0x44
	KEY_KP_NUMLOCK,	//0x45
	KEY_SCROLLLOCK,	//0x46
	KEY_HOME,		//0x47
	KEY_KP_8,		//0x48	//keypad up arrow
	KEY_PAGEUP,		//0x49
	KEY_KP_2,		//0x50	//keypad down arrow
	KEY_KP_3,		//0x51	//keypad page down
	KEY_KP_0,		//0x52	//keypad insert key
	KEY_KP_DECIMAL,	//0x53	//keypad delete key
	KEY_UNKNOWN,	//0x54
	KEY_UNKNOWN,	//0x55
	KEY_UNKNOWN,	//0x56
	KEY_F11,		//0x57
	KEY_F12			//0x58
};

const int INVALID_CODE = 0;

static bool _isCap = false;
static bool _isNum = false;
static bool _isScroll = false;

static bool _isCtrl = false;
static bool _isShift = false;
static bool _isAlt = false;

static char _scanCode = INVALID_CODE;

uint8_t kybrd_ctl_read_status()
{
	return inportb(KYBRD_CTL_STATUS_PORT);
}

void kybrd_ctl_send_command(uint8_t cmd)
{
	while(1)
	{
		if((kybrd_ctl_read_status() & KYBRD_CTRL_STATS_MASK_IN_BUF) == 0)
			break;
	}

	outportb(KYBRD_CTL_CMD_PORT,cmd);
}

uint8_t kybrd_enc_read_buf()
{
	return inportb(KYBRD_ENC_INPUT_PORT);
}

void kybrd_enc_send_command(uint8_t cmd)
{
	while(1)
		if((kybrd_ctl_read_status() & KYBRD_CTRL_STATS_MASK_IN_BUF) == 0)
			break;

	outportb(KYBRD_ENC_CMD_PORT,cmd);
}

void kybrd_set_leds(bool cap,bool num,bool scroll)
{
	uint8_t data = 0;

	data = (scroll) ? (data | 1) : (data & 1);
	data = (num) ? (num | 2) : (num & 2);
	data = (cap) ? (num | 4) : (num & 4);

	kybrd_enc_send_command(KYBRD_ENC_CMD_SET_LED);
	kybrd_enc_send_command(data);
}

void _cdecl kybrd_i86_handler()
{
	_asm add esp, 12
	_asm pushad
	_asm cli
	static bool _extended = false;
	int code;
	if(kybrd_ctl_read_status() & KYBRD_CTRL_STATS_MASK_OUT_BUF)
	{
		code = kybrd_enc_read_buf();
		if(code == 0xE0 || code == 0xE1)
			_extended = true;
		else
		{
			_extended = false;

			if(code & 0x80)
			{
				code -= 0x80;

				int key = _kkybrd_scancode_std[code];
				switch (key)
				{
				case KEY_LCTRL:
				case KEY_RCTRL:
					_isCtrl = false;
					break;
				case KEY_LALT:
				case KEY_RALT:
					_isAlt = false;
					break;
				case KEY_LSHIFT:
				case KEY_RSHIFT:
					_isShift = false;
					break;
				}
			}
			else
			{
				_scanCode = code;
				int key = _kkybrd_scancode_std[code];

				switch (key)
				{
				case KEY_LCTRL:
				case KEY_RCTRL:
					_isCtrl = true;
					break;
				case KEY_LALT:
				case KEY_RALT:
					_isAlt = true;
					break;
				case KEY_LSHIFT:
				case KEY_RSHIFT:
					_isShift = true;
					break;
				case KEY_CAPSLOCK:
					_isCap = _isCap ? false : true;
					kybrd_set_leds(_isCap,_isNum,_isScroll);
					break;
				case KEY_KP_NUMLOCK:
					_isNum = _isNum ? false : true;
					kybrd_set_leds(_isCap,_isNum,_isScroll);
					break;
				case KEY_SCROLLLOCK:
					_isScroll = _isScroll ? false : true;
					kybrd_set_leds(_isCap,_isNum,_isScroll);
					break;
				}
			}
		}
	}
	interrupt_done(0);

	_asm sti
	_asm popad
	_asm iretd
}

KEYCODE kybrd_get_last_key()
{
	return (_scanCode != INVALID_CODE) ? ((KEYCODE)_kkybrd_scancode_std[_scanCode]) : (KEY_UNKNOWN);
}

bool kybrd_get_alt()
{
	return _isAlt;
}

bool kybrd_get_shift()
{
	return _isShift;
}

bool kybrd_get_ctrl()
{
	return _isCtrl;
}

bool kybrd_get_capLock()
{
	return _isCap;
}

bool kybrd_get_numLock()
{
	return _isNum;
}

bool kybrd_get_scrollLock()
{
	return _isScroll;
}

char kybrd_keycode_to_ascii(KEYCODE code)
{

	uint8_t key = code;

	if(isascii(key))
	{
		if(_isShift || _isCap)
			if(key >= 'a' && key <= 'z')
				key -= 32;
		if (_isShift)
			if (key >= '0' && key <= '9')
				switch (key) {

				case '0':
					key = KEY_RIGHTPARENTHESIS;
					break;
				case '1':
					key = KEY_EXCLAMATION;
					break;
				case '2':
					key = KEY_AT;
					break;
				case '3':
					key = KEY_EXCLAMATION;
					break;
				case '4':
					key = KEY_HASH;
					break;
				case '5':
					key = KEY_PERCENT;
					break;
				case '6':
					key = KEY_CARRET;
					break;
				case '7':
					key = KEY_AMPERSAND;
					break;
				case '8':
					key = KEY_ASTERISK;
					break;
				case '9':
					key = KEY_LEFTPARENTHESIS;
					break;
			}
			else {

				switch (key) {
				case KEY_COMMA:
					key = KEY_LESS;
					break;

				case KEY_DOT:
					key = KEY_GREATER;
					break;

				case KEY_SLASH:
					key = KEY_QUESTION;
					break;

				case KEY_SEMICOLON:
					key = KEY_COLON;
					break;

				case KEY_QUOTE:
					key = KEY_QUOTEDOUBLE;
					break;

				case KEY_LEFTBRACKET :
					key = KEY_LEFTCURL;
					break;

				case KEY_RIGHTBRACKET :
					key = KEY_RIGHTCURL;
					break;

				case KEY_GRAVE:
					key = KEY_TILDE;
					break;

				case KEY_MINUS:
					key = KEY_UNDERSCORE;
					break;

				case KEY_PLUS:
					key = KEY_EQUAL;
					break;

				case KEY_BACKSLASH:
					key = KEY_BAR;
					break;
				}
			}

			//! return the key
			return key;
	}

	return 0;
}

void kybrd_initialize()
{
	setvect(33,kybrd_i86_handler);

	_isCap = false;
	_isNum = false;
	_isScroll = false;

	kybrd_set_leds(_isCap,_isNum,_isScroll);

	_scanCode = INVALID_CODE;

	_isShift = false;
	_isAlt = false;
	_isCtrl = false;
}

void kybrd_discard_last_key()
{
	_scanCode = INVALID_CODE;
}